package obecto.task.animation
{
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import obecto.task.BaseInterruptibleTask;
	import obecto.tween.AbstractTween;
	import obecto.tween.FrameTween;
	import obecto.tween.TimeTween;
	import obecto.tween.TweensManager;
	import obecto.tween.easing.PennerEasing;
	import obecto.utility.DictionaryUtil;

	[DefaultProperty("tweeningProperties")]
	
	public class PropertiesTweenAnimation extends BaseInterruptibleTask
	{
		private var _tween : AbstractTween;
		
		private var _target : Object;
		[Bindable]
		public function get target() : Object 
		{
			return _target;
		}
		public function set target(value : Object) : void
		{
			_target = value;
			initializeTweenProperties();
		}
		
		private var _duration : Number;
		[Bindable]
		public function set duration(value : Number) : void
		{
			_duration = value;
			roundTimeForFrameTween();
		}
		public function get duration() : Number
		{
			return _duration;
		}
		
		private var _tweenType : PropertiesTweenAnimationType;
		[Bindable]
		public function set tweenType(value : PropertiesTweenAnimationType) : void
		{
			_tweenType = value;
			roundTimeForFrameTween();
		}
		public function get tweenType() : PropertiesTweenAnimationType
		{
			return _tweenType;
		}
		
		[Bindable]
		private var _tweeningProperties : Array;
		public function set tweeningProperties(value : Array) : void
		{
			_tweeningProperties = value;
			
			initializeTweenProperties();
		}
		public function get tweeningProperties() : Array
		{
			return _tweeningProperties;
		}
		
		private var _startValues : Dictionary;
		[Bindable("tweeningPropertiesChanged")]
		public function get startValues() : Dictionary
		{
			return _startValues;
		}
		
		private var _endValues : Dictionary;
		[Bindable("tweeningPropertiesChanged")]
		public function endValues() : Dictionary
		{
			return _endValues;
		}
		
		[Bindable]
		public var applyStartValuesOnStart : Boolean = true;
		
		[Bindable]
		public var easingFunction : Function = PennerEasing.linear;
		
		public var updateDelegate : Function;
		
		private var oldTargetCacheAsBitmatValue : Boolean;
		private var targetCacheAsBitmapPolicyActual : AnimationTargetCacheAsBitmapPolicy;
		public var targetCacheAsBitmapPolicy : AnimationTargetCacheAsBitmapPolicy;
		
		private var _cuePoints:Array;
		
		public function PropertiesTweenAnimation()
		{
			super();
			
			//_tweenType = PropertiesTweenAnimationType.FRAME_TWEEN;
			_cuePoints = new Array();
		}
		
		public function getCuePointEventNames() : Array 
		{
			var result : Array = new Array();
			
			for (var i : Number = 0; i < _cuePoints.length; i++)
			{
				result.push(_cuePoints[i].event);
			}
			
			return result;
		}
		
		public function addCuePoint(aPropertyValueReached:Number, aEventName:String) : void
		{
			_cuePoints.push({position:aPropertyValueReached, event:aEventName});
			_cuePoints.sortOn("position");
		}

		override protected function startActual() : void
		{
			if (_target is DisplayObject)
			{
				oldTargetCacheAsBitmatValue = (_target as DisplayObject).cacheAsBitmap;
			}
			
			// ensure the most current start values are gathered:
			initializeStartValues(true);
			
			if(applyStartValuesOnStart)
			{
				applyPropertyValues(_startValues);	
			}
			
			determineActualSubjectCacheAsBitmapPolicy();
			setTargetCacheAsBitmapProperty();
			
			initializeTween();
			TweensManager.instance.doTween(_tween);
		}		
		
		private var _reverseTween : AbstractTween;
		override protected function reverseActual() : void
		{
			if (_target is DisplayObject)
			{
				oldTargetCacheAsBitmatValue = (_target as DisplayObject).cacheAsBitmap;
			}
			
			if(running)
			{
				TweensManager.instance.stopTween(_tween);				
			}
			
			determineActualSubjectCacheAsBitmapPolicy();
			setTargetCacheAsBitmapProperty();
			
			initializeReverseTween();
			TweensManager.instance.doTween(_reverseTween);
		}
		
		override protected function forwardActual() : void
		{
			TweensManager.instance.stopTween(_reverseTween);
			
			determineActualSubjectCacheAsBitmapPolicy();
			setTargetCacheAsBitmapProperty();
			
			initializeForwardTween();
			TweensManager.instance.doTween(_tween);			
		}		
		
		override public function interrupt() : void
		{
			TweensManager.instance.stopTween(_tween);
			TweensManager.instance.stopTween(_reverseTween);
			
			super.interrupt();
		} 

		override public function end() : void
		{
			TweensManager.instance.stopTween(_tween);
			TweensManager.instance.stopTween(_reverseTween);
			
			applyPropertyValues(_endValues);
			
			super.end();
		} 

		override public function rollback() : void
		{
			TweensManager.instance.stopTween(_tween);
			TweensManager.instance.stopTween(_reverseTween);
			
			applyPropertyValues(_startValues);
			
			super.rollback();
		} 
		
        public function get durationElapsed():Number
        {
            return _tween.durationElapsed;
        }
        
		private function initializeStartValues(setTweenPropertiesValues : Boolean) : void
		{
			if(_tweeningProperties && _target)
			{
				_startValues = new Dictionary();
				
				for each(var item : TweenProperty in _tweeningProperties)
				{
					if (isNaN(item.startValue) || item.startValue == undefined)
					{
						_startValues[item.name] = _target[item.name];

						if(setTweenPropertiesValues)
						{
							item.startValue = _startValues[item.name];
						}
					}
					else
					{
						_startValues[item.name] = item.startValue;
					}
				}
			}
		}
		
		private function initializeEndValues() : void
		{
			_endValues = new Dictionary();

			for each(var item : TweenProperty in _tweeningProperties)
			{
				_endValues[item.name] = item.endValue;
			}
		}
		
		private function applyPropertyValues(propertyValues : Dictionary) : void
		{
			for(var key : String in propertyValues)
			{
				_target[key] = propertyValues[key];
			}
		}
		
		private function initializeTweenProperties() : void
		{
			initializeStartValues(false);
			initializeEndValues();
			initializeTweenPropertiesListeners();
			
			dispatchEvent(new Event("tweeningPropertiesChanged"));
		}
		
		private function initializeTweenPropertiesListeners() : void 
		{
			for each(var item : TweenProperty in _tweeningProperties)
			{
				item.addEventListener("startValueChange", handleStartValueChange);
				item.addEventListener("endValueChange", handleEndValueChange);
			}
		}
		
		private function handleStartValueChange(e : Event) : void 
		{
			var item : TweenProperty = TweenProperty(e.target);
			_startValues[item.name] = item.startValue;
		}
		
		private function handleEndValueChange(e : Event) : void 
		{
			var item : TweenProperty = TweenProperty(e.target);
			_endValues[item.name] = item.endValue;
		}
		
		private function initializeTween() : void
		{
			_tween = createTween(_endValues);
			
			_tween.duration = duration;
			_tween.easingEquation = easingFunction;
			_tween.tweenUpdateDelegate = handleTweenUpdate;
			_tween.tweenFinishedDelegate = handleTweenFinished;
		}
		
		private function initializeReverseTween() : void
		{
			_reverseTween = createTween(_startValues);
			
			if(_tween)
			{
				_reverseTween.duration = (_duration - _tween.duration) + _tween.durationElapsed;
			}
			else
			{
				_reverseTween.duration = _duration;
			}
			
			if(_reverseTween.duration < 0)
			{
				_reverseTween.duration = 0.1;
			}
			_reverseTween.easingEquation = easingFunction;
			_reverseTween.tweenUpdateDelegate = handleTweenUpdate;
			_reverseTween.tweenFinishedDelegate = handleReverseTweenFinished;
		}
		
		private function initializeForwardTween() : void
		{
			_tween = createTween(_endValues);
			
			_tween.duration = (_duration - _reverseTween.duration) + _reverseTween.durationElapsed;
			if(_tween.duration < 0)
			{
				_tween.duration = 0.1;
			}
			_reverseTween.easingEquation = easingFunction;
			_tween.tweenUpdateDelegate = handleTweenUpdate;
			_tween.tweenFinishedDelegate = handleTweenFinished;
		}
		
		private function createTween(tweeningProperties : Dictionary) : AbstractTween
		{
			var tween : AbstractTween;
			if (_tweenType == PropertiesTweenAnimationType.FRAME_TWEEN)
			{
				tween = new FrameTween(target, tweeningProperties);
			}
			else if (_tweenType == PropertiesTweenAnimationType.TIME_TWEEN)
			{
				tween = new TimeTween(target, tweeningProperties);
			}
			else
			{
				throw new Error("Invalid tween type.");
			}
			return tween;
		}
		
		private function determineActualSubjectCacheAsBitmapPolicy() : void
		{
			if(targetCacheAsBitmapPolicy == AnimationTargetCacheAsBitmapPolicy.AUTO)
			{
				if(_target is DisplayObject)
				{
					if (DictionaryUtil.containsAnyKey(["x", "y"], _endValues))
					{
						targetCacheAsBitmapPolicyActual = AnimationTargetCacheAsBitmapPolicy.FORCE_TRUE_WHILE_ANIMATING;
					}
					else
					{
						targetCacheAsBitmapPolicyActual = AnimationTargetCacheAsBitmapPolicy.LEAVE;
					}
					
					if (DictionaryUtil.containsAnyKey(["width", "height", "alpha", "scaleX", "scaleY", "rotation"], _endValues))
					{
						targetCacheAsBitmapPolicyActual = AnimationTargetCacheAsBitmapPolicy.FORCE_FALSE_WHILE_ANIMATING;
					}
					else
					{
						targetCacheAsBitmapPolicyActual = AnimationTargetCacheAsBitmapPolicy.LEAVE;
					}
				}
				else
				{
					targetCacheAsBitmapPolicyActual = AnimationTargetCacheAsBitmapPolicy.LEAVE;
				}
			}
			else
			{
				targetCacheAsBitmapPolicyActual = targetCacheAsBitmapPolicy;
			}
		}
		
		private function setTargetCacheAsBitmapProperty() : void
		{
			if (_target is DisplayObject)
			{
				if(targetCacheAsBitmapPolicyActual == AnimationTargetCacheAsBitmapPolicy.FORCE_TRUE_WHILE_ANIMATING)
				{
					(_target as DisplayObject).cacheAsBitmap = true;
				}
				else if(targetCacheAsBitmapPolicyActual == AnimationTargetCacheAsBitmapPolicy.FORCE_FALSE_WHILE_ANIMATING)
				{
					(_target as DisplayObject).cacheAsBitmap = false;
				}
			}
		}
		
		private function revertTargetCacheAsBitmapProperty() : void
		{
			if (_target is DisplayObject && 
				targetCacheAsBitmapPolicy != AnimationTargetCacheAsBitmapPolicy.LEAVE)
			{
				(_target as DisplayObject).cacheAsBitmap = oldTargetCacheAsBitmatValue;
			}
		}

		private function roundTimeForFrameTween() : void
		{
			if (_tweenType == PropertiesTweenAnimationType.FRAME_TWEEN)
			{
				_duration = Math.round(_duration);	
			}
		}
		
		private function handleTweenUpdate() : void
		{
			if(_cuePoints.length > 0)
			{
				var currentPosition:Number = _target[tweeningProperties[0]];
				
				for(var i:Number = _cuePoints.length - 1; i >= 0; i--)
				{
					if (currentPosition >= _cuePoints[i].position &&
						(i == _cuePoints.length - 1 || currentPosition < _cuePoints[i+1].position))
					{
						dispatchEvent(new Event(_cuePoints[i].event));
						break;
					}
				}
			}
			
			if(updateDelegate != null)
			{
				updateDelegate();
			}
		}
		
		private function handleTweenFinished() : void
		{
			if(updateDelegate != null)
			{
				updateDelegate();
			}
			complete();
		}
		
		private function handleReverseTweenFinished() : void
		{
			completeReverse();
		}
		
		override protected function complete() : void
		{
			revertTargetCacheAsBitmapProperty();	
			super.complete();
		} 

		override protected function completeReverse() : void
		{
			revertTargetCacheAsBitmapProperty();	
			super.completeReverse();
		} 
	}
}